import * as Cesium from 'cesium';
import { FrustumCommandsGaussianSplatting } from './fixFrustuCommands';
//@ts-ignore
const { SceneMode, CullingVolume, Interval, Pass, defined, ShadowMap, Math: CesiumMath,
    //@ts-ignore
    ClearCommand, FrustumCommands
} = Cesium
const scratchCullingVolume = new CullingVolume();
const scratchNearFarInterval = new Interval();
export default function fixView() {
    //@ts-ignore
    Cesium.View.prototype.createPotentiallyVisibleSet = function (scene: any) {
        const { frameState } = scene;
        const { camera, commandList, shadowState } = frameState;
        const { positionWC, directionWC, frustum } = camera;

        const computeList = scene._computeCommandList;
        const overlayList = scene._overlayCommandList;

        if (scene.debugShowFrustums) {
            this.debugFrustumStatistics = {
                totalCommands: 0,
                commandsInFrustums: {},
            };
        }

        const frustumCommandsList = this.frustumCommandsList;
        for (let n = 0; n < frustumCommandsList.length; ++n) {
            // Pass.NUMBER_OF_PASSES原始是11，增加高斯泼溅模型后加1，原Pass只读无法修改
            // for (let p = 0; p < Pass.NUMBER_OF_PASSES + 1; ++p) {
            for (let p = 0; p < 12; ++p) {
                frustumCommandsList[n].indices[p] = 0;
            }
        }

        computeList.length = 0;
        overlayList.length = 0;

        const commandExtents = this._commandExtents;
        const commandExtentCapacity = commandExtents.length;
        let commandExtentCount = 0;

        let near = +Number.MAX_VALUE;
        let far = -Number.MAX_VALUE;

        const { shadowsEnabled } = shadowState;
        let shadowNear = +Number.MAX_VALUE;
        let shadowFar = -Number.MAX_VALUE;
        let shadowClosestObjectSize = Number.MAX_VALUE;

        const occluder =
            frameState.mode === SceneMode.SCENE3D ? frameState.occluder : undefined;

        // get user culling volume minus the far plane.
        let { cullingVolume } = frameState;
        const planes = scratchCullingVolume.planes;
        for (let k = 0; k < 5; ++k) {
            planes[k] = cullingVolume.planes[k];
        }
        cullingVolume = scratchCullingVolume;

        for (let i = 0; i < commandList.length; ++i) {
            const command = commandList[i];
            const { pass, boundingVolume } = command;

            if (pass === Pass.COMPUTE) {
                computeList.push(command);
            } else if (pass === Pass.OVERLAY) {
                overlayList.push(command);
            } else {
                let commandNear;
                let commandFar;

                if (defined(boundingVolume)) {
                    if (!scene.isVisible(cullingVolume, command, occluder)) {
                        continue;
                    }

                    const nearFarInterval = boundingVolume.computePlaneDistances(
                        positionWC,
                        directionWC,
                        scratchNearFarInterval,
                    );
                    commandNear = nearFarInterval.start;
                    commandFar = nearFarInterval.stop;
                    near = Math.min(near, commandNear);
                    far = Math.max(far, commandFar);

                    // Compute a tight near and far plane for commands that receive shadows. This helps compute
                    // good splits for cascaded shadow maps. Ignore commands that exceed the maximum distance.
                    // When moving the camera low LOD globe tiles begin to load, whose bounding volumes
                    // throw off the near/far fitting for the shadow map. Only update for globe tiles that the
                    // camera isn't inside.
                    if (
                        shadowsEnabled &&
                        command.receiveShadows &&
                        //@ts-ignore
                        commandNear < ShadowMap.MAXIMUM_DISTANCE &&
                        !(pass === Pass.GLOBE && commandNear < -100.0 && commandFar > 100.0)
                    ) {
                        // Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object.
                        const size = commandFar - commandNear;
                        if (pass !== Pass.GLOBE && commandNear < 100.0) {
                            shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size);
                        }
                        shadowNear = Math.min(shadowNear, commandNear);
                        shadowFar = Math.max(shadowFar, commandFar);
                    }
                } else if (command instanceof ClearCommand) {
                    // Clear commands don't need a bounding volume - just add the clear to all frustums.
                    commandNear = frustum.near;
                    commandFar = frustum.far;
                } else {
                    // If command has no bounding volume we need to use the camera's
                    // worst-case near and far planes to avoid clipping something important.
                    commandNear = frustum.near;
                    commandFar = frustum.far;
                    near = Math.min(near, commandNear);
                    far = Math.max(far, commandFar);
                }

                let extent = commandExtents[commandExtentCount];
                if (!defined(extent)) {
                    extent = commandExtents[commandExtentCount] = new CommandExtent();
                }
                extent.command = command;
                extent.near = commandNear;
                extent.far = commandFar;
                commandExtentCount++;
            }
        }

        if (shadowsEnabled) {
            shadowNear = Math.min(Math.max(shadowNear, frustum.near), frustum.far);
            shadowFar = Math.max(Math.min(shadowFar, frustum.far), shadowNear);
            // Use the computed near and far for shadows
            shadowState.nearPlane = shadowNear;
            shadowState.farPlane = shadowFar;
            shadowState.closestObjectSize = shadowClosestObjectSize;
        }

        updateFrustums(this, scene, near, far);

        for (let c = 0; c < commandExtentCount; c++) {
            insertIntoBin(this, scene, commandExtents[c]);
        }

        // Dereference old commands
        if (commandExtentCount < commandExtentCapacity) {
            for (let c = commandExtentCount; c < commandExtentCapacity; c++) {
                const commandExtent = commandExtents[c];
                if (!defined(commandExtent.command)) {
                    // If the command is undefined, it's assumed that all
                    // subsequent commmands were set to undefined as well,
                    // so no need to loop over them all
                    break;
                }
                commandExtent.command = undefined;
            }
        }

        const numFrustums = frustumCommandsList.length;
        const { frustumSplits } = frameState;
        frustumSplits.length = numFrustums + 1;
        for (let j = 0; j < numFrustums; ++j) {
            frustumSplits[j] = frustumCommandsList[j].near;
            if (j === numFrustums - 1) {
                frustumSplits[j + 1] = frustumCommandsList[j].far;
            }
        }
    };
}
//#region 原始代码
function updateFrustums(view: any, scene: any, near: any, far: any) {
    const { frameState } = scene;
    const { camera, useLogDepth } = frameState;
    const farToNearRatio = useLogDepth
        ? scene.logarithmicDepthFarToNearRatio
        : scene.farToNearRatio;
    const is2D = scene.mode === SceneMode.SCENE2D;
    const nearToFarDistance2D = scene.nearToFarDistance2D;

    // Extend the far plane slightly further to prevent geometry clipping against the far plane.
    far *= 1.0 + CesiumMath.EPSILON2;

    // The computed near plane must be between the user defined near and far planes.
    // The computed far plane must between the user defined far and computed near.
    // This will handle the case where the computed near plane is further than the user defined far plane.
    near = Math.min(Math.max(near, camera.frustum.near), camera.frustum.far);
    far = Math.max(Math.min(far, camera.frustum.far), near);

    let numFrustums;
    if (is2D) {
        // The multifrustum for 2D is uniformly distributed. To avoid z-fighting in 2D,
        // the camera is moved to just before the frustum and the frustum depth is scaled
        // to be in [1.0, nearToFarDistance2D].
        far = Math.min(far, camera.position.z + scene.nearToFarDistance2D);
        near = Math.min(near, far);
        numFrustums = Math.ceil(
            Math.max(1.0, far - near) / scene.nearToFarDistance2D,
        );
    } else {
        // The multifrustum for 3D/CV is non-uniformly distributed.
        numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
    }

    const { frustumCommandsList } = view;
    frustumCommandsList.length = numFrustums;
    for (let m = 0; m < numFrustums; ++m) {
        let curNear;
        let curFar;

        if (is2D) {
            curNear = Math.min(
                far - nearToFarDistance2D,
                near + m * nearToFarDistance2D,
            );
            curFar = Math.min(far, curNear + nearToFarDistance2D);
        } else {
            curNear = Math.max(near, Math.pow(farToNearRatio, m) * near);
            curFar = Math.min(far, farToNearRatio * curNear);
        }
        let frustumCommands = frustumCommandsList[m];
        if (!defined(frustumCommands)) {
            frustumCommands = frustumCommandsList[m] = new FrustumCommandsGaussianSplatting(
                curNear,
                curFar,
            );
        } else {
            frustumCommands.near = curNear;
            frustumCommands.far = curFar;
        }
    }
}
function insertIntoBin(view: any, scene: any, commandExtent: any) {
    const { command, near, far } = commandExtent;

    if (scene.debugShowFrustums) {
        command.debugOverlappingFrustums = 0;
    }

    const { frustumCommandsList } = view;

    for (let i = 0; i < frustumCommandsList.length; ++i) {
        const frustumCommands = frustumCommandsList[i];

        if (near > frustumCommands.far) {
            continue;
        }

        if (far < frustumCommands.near) {
            break;
        }

        const pass = command.pass;
        const index = frustumCommands.indices[pass]++;
        frustumCommands.commands[pass][index] = command;

        if (scene.debugShowFrustums) {
            command.debugOverlappingFrustums |= 1 << i;
        }

        if (command.executeInClosestFrustum) {
            break;
        }
    }

    if (scene.debugShowFrustums) {
        const { debugFrustumStatistics } = view;
        const { debugOverlappingFrustums } = command;
        const cf = debugFrustumStatistics.commandsInFrustums;
        cf[debugOverlappingFrustums] = defined(cf[debugOverlappingFrustums])
            ? cf[debugOverlappingFrustums] + 1
            : 1;
        ++debugFrustumStatistics.totalCommands;
    }

    scene.updateDerivedCommands(command);
}
class CommandExtent {
    command: undefined;
    near: undefined;
    far: undefined;
}
//#endregion